/* Copyright (c) 2009-2011 Matthias Kaeppler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mdroid.support.images.remote;

import java.util.concurrent.ExecutorService;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.mdroid.app.MLibrary;
import com.mdroid.support.cache.ImageCache;

/**
 * Realizes a background image loader that downloads an image from a URL, optionally backed by a
 * two-level FIFO cache. If the image to be loaded is present in the cache, it is set immediately on
 * the given view. Otherwise, a thread from a thread pool will be used to download the image in the
 * background and set the image on the view as soon as it completes.
 * 
 * @author Matthias Kaeppler
 */
public class RemoteImageLoader {

    // expire images after a day
    // TODO: this currently only affects the in-memory cache, so it's quite pointless
    private static final int DEFAULT_NUM_RETRIES = 3;
    private static final int DEFAULT_BUFFER_SIZE = 65536;

    private ExecutorService executor;
    private ImageCache imageCache;
    private int numRetries = DEFAULT_NUM_RETRIES;
    private int defaultBufferSize = DEFAULT_BUFFER_SIZE;

    private Drawable errorDrawable;

    /**
     * Creates a new ImageLoader that is backed by an {@link ImageCache}. The cache will by default
     * cache to the device's external storage, and expire images after 1 day. You can set useCache
     * to false and then supply your own image cache instance via {@link #setImageCache(ImageCache)}
     * , or fine-tune the default one through {@link #getImageCache()}.
     * 
     * @param context
     *            the current context
     */
    public RemoteImageLoader(Context context) {
        executor = MLibrary.Instance().getExecutor();
        imageCache = MLibrary.Instance().getImageCache();
    }

    /**
     * @param numAttempts
     *            how often the image loader should retry the image download if network connection
     *            fails
     */
    public void setMaxDownloadAttempts(int numAttempts) {
        numRetries = numAttempts;
    }

    /**
     * If the server you're loading images from does not report file sizes via the Content-Length
     * header, then you can use this method to tell the downloader how much space it should allocate
     * by default when downloading an image into memory.
     * 
     * @param defaultBufferSize
     *            how big the buffer should be into which the image file is read. This should be big
     *            enough to hold the largest image you expect to download
     */
    public void setDefaultBufferSize(int defaultBufferSize) {
        this.defaultBufferSize = defaultBufferSize;
    }

    public void setDownloadFailedDrawable(Drawable drawable) {
        this.errorDrawable = drawable;
    }
    
    public void setImageCache(ImageCache imageCache) {
        this.imageCache = imageCache;
    }

    /**
     * Clears the image cache, if it's used. A good candidate for calling in
     * {@link android.app.Application#onLowMemory()}.
     */
    public void clearImageCache() {
        if (imageCache != null) {
            imageCache.clear();
        }
    }

    /**
     * Returns the image cache backing this image loader.
     * 
     * @return the {@link ImageCache}
     */
    public ImageCache getImageCache() {
        return imageCache;
    }

    /**
     * Triggers the image loader for the given image and view. The image loading will be performed
     * concurrently to the UI main thread, using a fixed size thread pool. The loaded image will be
     * posted back to the given ImageView upon completion. This method will the default
     * {@link RemoteImageLoaderHandler} to process the bitmap after downloading it.
     * 
     * @param imageUrl
     *            the URL of the image to download
     * @param imageView
     *            the ImageView which should be updated with the new image
     * @param progressBar the ProgressBar for show download progress
     */
    public void loadImage(String imageUrl, ImageView imageView) {
        loadImage(imageUrl, imageView, null, new RemoteImageLoaderHandler(imageView, imageUrl, null, errorDrawable, null));
    }

    /**
     * Triggers the image loader for the given image and view. The image loading will be performed
     * concurrently to the UI main thread, using a fixed size thread pool. The loaded image will be
     * posted back to the given ImageView upon completion.
     * 
     * @param imageUrl
     *            the URL of the image to download
     * @param imageView
     *            the ImageView which should be updated with the new image
     * @param isIndeterminate
     *            the progressBar is Indeterminate
     * @param handler
     *            the handler that will process the bitmap after completion
     */
    public void loadImage(String imageUrl, ImageView imageView, ProgressBar progressBar, RemoteImageLoaderHandler handler) {
        if (imageView != null) {
            if (imageUrl == null) {
                // In a ListView views are reused, so we must be sure to remove the tag that could
                // have been set to the ImageView to prevent that the wrong image is set.
                handler.handleImageLoaded(null, null);
            }
            imageView.setTag(imageUrl);
        }

        boolean isIndeterminate = progressBar == null ? true : progressBar.isIndeterminate();
        if (imageCache != null && imageCache.containsKeyInMemory(imageUrl)) {
            // do not go through message passing, handle directly instead
            if (!isIndeterminate) {
                handler.handleImageProgress(100);
            }
            Bitmap bitmap = imageCache.getBitmap(imageUrl);
            if (bitmap != null) {
                handler.handleImageLoaded(bitmap, null);
                return;
            }
        }
        
        executor.execute(new RemoteImageLoaderJob(imageUrl, handler, imageCache, numRetries,
                defaultBufferSize, isIndeterminate));
    }
}
